#include <libtorrent/session.hpp>
#include <libtorrent/bencode.hpp>
#include <libtorrent/extensions/ut_pex.hpp>
#include <fstream>
#include <glib/gstdio.h>
#include "nls.h"
#include "operations.h"
#include "ttorrent.h"
extern "C" {
#include "views.h"
}

gchar * get_sha(libtorrent::torrent_info * ti)
{
  gchar buf[60];
  const guchar* iter = ti->info_hash().begin();
  int i = 0;


  while(iter != ti->info_hash().end())
  {
    g_snprintf(buf+i, sizeof(buf)-i, "%.2hhx", *iter);
    ++iter;
    i+=2;
  }

  return g_strndup(buf, i);
}

gpointer fill_info_view(libtorrent::torrent_info * ti)
{
  GtkTable * table = GTK_TABLE(gtk_table_new(8, 2, FALSE));
  GtkWidget * label;
  const guchar* iter;
  gchar  buf[60] = {0};
  gchar *markup;

  gtk_table_set_col_spacings(table, 4);
  gtk_table_set_row_spacings(table, 2);

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("<span weight='bold'>%s</span>", _("Name:"));
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 0, 1, 0, 1);
  g_free(markup);

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("<span>%s</span>", ti->name().c_str());
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 1, 4, 0, 1);
  g_free(markup);

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("<span weight='bold'>%s</span>", _("SHA:"));
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 0, 1, 1, 2);
  g_free(markup);

  iter = ti->info_hash().begin();
  int i = 0;
  while(iter != ti->info_hash().end())
  {
    g_snprintf(buf+i, sizeof(buf)-i, "%.2hhx", *iter);
    ++iter;
    i+=2;
  }
  buf[++i] = 0;

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("%s", buf);
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 1, 4, 1, 2);
  g_free(markup);

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("<span weight='bold'>%s</span>", _("Size:"));
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 0, 1, 2, 3);
  g_free(markup);

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("<span>%.1lf KB</span>",
    (gdouble)ti->total_size()/1024
  );
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 1, 2, 2, 3);
  g_free(markup);

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("<span weight='bold'>%s</span>", _("Pieces:"));
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 2, 3, 2, 3);
  g_free(markup);

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("<span>%d x %.1lf KB</span>",
    ti->num_pieces(),
    (gdouble) ti->piece_length()/1024
  );
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 3, 4, 2, 3);
  g_free(markup);

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("<span weight='bold'>%s</span>", _("Creator:"));
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 0, 1, 3, 4);
  g_free(markup);

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("<span>%s</span>", ti->creator().c_str());
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 1, 2, 3, 4);
  g_free(markup);

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("<span weight='bold'>%s</span>", _("Created:"));
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 2, 3, 3, 4);
  g_free(markup);

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("<span>%s</span>", ""/*boost::posix_time::to_simple_string(*ti->creation_date()).c_str()*/ );
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 3, 4, 3, 4);
  g_free(markup);

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("<span weight='bold'>%s</span>", _("Comment:"));
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 0, 1, 4, 5);
  g_free(markup);

  label = gtk_label_new(NULL);
  markup = g_markup_printf_escaped("<span>%s</span>", ti->comment().c_str());
  gtk_label_set_markup(GTK_LABEL(label), markup);
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.0);
  gtk_table_attach_defaults(table, label, 1, 4, 4, 5);
  g_free(markup);

  g_object_ref(G_OBJECT(table));
  return (gpointer) table;
}

gpointer fill_files_model(libtorrent::torrent_info * ti, gpointer edited)
{
  gint i = 0;
  GtkListStore * store;
  GtkTreeModel * model;
  GtkTreeIter iter;
  libtorrent::torrent_info::file_iterator  file_iter;

  store = create_files_store();

  file_iter = ti->begin_files();

  while(file_iter != ti->end_files())
  {
    gtk_list_store_append(store, &iter);
    gtk_list_store_set(
      store,
      &iter,
      COL_F_ORDER, ++i,
      COL_F_PRIORITY, 1,
      COL_F_NEW_PRI, 1,
      COL_F_SIZE, file_iter->size,
      COL_F_PATH, file_iter->path.string().c_str(),
      COL_F_EDITED, edited,
      -1
    );
    ++file_iter;
  }

  model = gtk_tree_model_sort_new_with_model(GTK_TREE_MODEL(store));
  g_object_unref(store);
  return model;
}

extern "C" void
init_session(TTorrent *tt)
{
  libtorrent::session * sess;

  // workaround for bad checking (spaces not allowed)
  boost::filesystem::path::default_name_check (boost::filesystem::native);
  sess = new libtorrent::session ();
  tt->sess = (gpointer) sess;
  tt->timer = 0;
  apply_settings(tt);
}

extern "C" void
apply_settings(TTorrent *tt)
{
  libtorrent::session * sess = (libtorrent::session *) tt->sess;

  sess->listen_on (std::make_pair (tt->settings->port_min, tt->settings->port_max));
  if(tt->settings->use_pex > 0)
    sess->add_extension(libtorrent::create_ut_pex_plugin);
  sess->set_max_connections(tt->settings->connections);
  sess->set_max_uploads(tt->settings->uploads);
  sess->set_upload_rate_limit( tt->settings->up_speed );
  sess->set_download_rate_limit( tt->settings->down_speed );
}

extern "C" void close_session(TTorrent *ttorrent)
{
  delete (libtorrent::session *) ttorrent->sess;
  delete (std::vector< libtorrent::peer_info> *) ttorrent->peer_info;
  delete (std::vector< libtorrent::partial_piece_info> *) ttorrent->block_info;
  ttorrent->sess = NULL;
}

extern "C" void
add_torrents(TTorrent * ttorrent, gchar** files)
{
  gint fnum = g_strv_length(files);
  gint x;
  gchar * home;
  gchar * buf;

  home = g_get_current_dir();

  for ( x = 0; x < fnum; ++x )
  {
    if(g_path_is_absolute(files[x]))
      buf = g_strdup_printf("%s\n", files[x]);
    else
      buf = g_build_path(home, files[x], NULL);

    if(!add_torrent(ttorrent, buf, NULL))
      g_printerr(_("Can't add torrent: %s\n"), buf);
    g_free(buf);
  }

  g_free(home);
  g_strfreev(files);
}

extern "C" gboolean
add_torrent(TTorrent * ttorrent, const gchar* fileName, TSaveState * tss)
{
    GtkTreeModel *model;
    GtkTreeModel *child_model;
    GtkTreeIter newrow;
    GtkTreeIter iter;
    GtkTreeSelection * sel;
    gboolean valid;
    gchar * sha = NULL;
    libtorrent::torrent_handle * th;
    libtorrent::torrent_info * ti;
    //boost::intrusive_ptr<libtorrent::torrent_info> * bip;

    try{
      std::ifstream in (fileName, std::ios_base::binary);
      in.unsetf (std::ios_base::skipws);

      libtorrent::entry entry =
        libtorrent::bdecode (std::istream_iterator < char >(in),
        std::istream_iterator < char >()
      );

      ti = new libtorrent::torrent_info (entry);
      th = new libtorrent::torrent_handle ();
      //bip = new boost::intrusive_ptr<libtorrent::torrent_info>(*ti, false);

      *th = ((libtorrent::session *)
        ttorrent->sess)->add_torrent(
          boost::intrusive_ptr<libtorrent::torrent_info>(ti, true),
          ttorrent->settings->save_dir
        );
      ++ttorrent->count;
      sha = get_sha(ti);

      if(!tss)
      {
        gchar * fname = g_build_filename(ttorrent->conf_dir, sha, NULL);

        std::ofstream out(fname, std::ios_base::binary);
        libtorrent::bencode(std::ostream_iterator<char>(out), entry);
        out.close();

        g_free(fname);
      }
    }
    catch(std::exception &e)
    {
      std::cerr << e.what () << "\n";
      if(sha) g_free(sha);
      return FALSE;
    }

    model = gtk_tree_view_get_model(ttorrent->torrents_view);

    child_model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(model));
    gtk_list_store_append (GTK_LIST_STORE(child_model), &newrow);
    gpointer pri = g_slice_new0(gint);
    if(!pri) return FALSE;
    gtk_list_store_set (GTK_LIST_STORE(child_model), &newrow,
      COL_T_HANDLE, th,
      COL_T_TABLE, fill_info_view( ti ),
      COL_T_FILES, fill_files_model( ti, pri ),
      COL_T_PRIORITIES, pri,
      COL_T_NUM_FILES, ti->num_files(),
      COL_T_NUMBER, ttorrent->count,
      COL_T_NAME, ti->name().c_str (),
      COL_T_SHA, sha,
      COL_T_SIZE, ti->total_size(),
      COL_T_STATE, S_ACTIVE,
      COL_T_SEEDS, -1,
      COL_T_PEERS, -1,
      COL_T_DOWN_SPEED, -1.0,
      COL_T_UP_SPEED, -1.0,
      COL_T_DOWN_PREV, (tss ? tss->downloaded : 0 ),
      COL_T_UP_PREV, ( tss ? tss->uploaded : 0 ),
      COL_T_RATIO, -1.0,
    -1);

    g_free(sha);
    sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(ttorrent->torrents_view));
    gtk_tree_model_sort_convert_child_iter_to_iter (GTK_TREE_MODEL_SORT(model), &iter, &newrow);
    gtk_tree_selection_select_iter(sel, &iter);

    return TRUE;
}

void remove_torrent ( GtkTreeModel * model, GtkTreeIter * iter, TTorrent * ttorrent )
{
  gint order;
  gboolean valid;

  libtorrent::torrent_handle * th;
  gchar * fname;
  gchar * sha;
  gint * pri;
  GError * err = NULL;

  gtk_tree_model_get(model, iter,
    COL_T_HANDLE, &th,
    COL_T_NUMBER, &order,
    COL_T_SHA, &sha,
    COL_T_PRIORITIES, &pri,
  -1);
  ttorrent->handle = NULL;
  valid = gtk_list_store_remove(GTK_LIST_STORE(model), iter);
  while(valid)
  {
    gtk_list_store_set(GTK_LIST_STORE(model), iter, COL_T_NUMBER, order++, -1);
    valid = gtk_tree_model_iter_next(model, iter);
  };

  --ttorrent->count;
  libtorrent::session* sess = (libtorrent::session*) ttorrent->sess;
  sess->remove_torrent(*th);
  fname = g_build_filename(ttorrent->conf_dir, sha, NULL);

  if(g_remove(fname) < 0)
    g_message(_("Can't remove file '%s'"), fname);

  g_free(fname);
  g_free(sha);
  g_slice_free(gint, pri);
  delete th;
}

extern "C" void
pause_resume_torrent(GtkTreeModel *model, GtkTreeIter * iter)
{
  libtorrent::torrent_handle * th;
  gtk_tree_model_get (model, iter, COL_T_HANDLE, &th, -1);
  if(th->is_paused())
  {
    th->resume();
    gtk_list_store_set (GTK_LIST_STORE (model), iter,
      COL_T_STATE, S_ACTIVE, -1);
  }
  else
  {
    th->pause();
    gtk_list_store_set (GTK_LIST_STORE (model), iter,
      COL_T_STATE, S_PAUSED,
      COL_T_SEEDS, -1, COL_T_PEERS, -1,
      COL_T_DOWN_SPEED, -1.0, COL_T_UP_SPEED, -1.0,
    -1);
  };
}

extern "C" void
prioritize_files(GtkTreeModel *tree, GtkTreeIter *tree_iter, int mode)
{
  libtorrent::torrent_handle * th;
  GtkTreeModel * model;
  GtkTreeIter iter;
  gboolean valid;
  gint num;

  gtk_tree_model_get(tree, tree_iter,
    COL_T_HANDLE, &th,
    COL_T_FILES, &model,
    COL_T_NUM_FILES, &num,
  -1);

  model = gtk_tree_model_sort_get_model(GTK_TREE_MODEL_SORT(model));
  valid = gtk_tree_model_get_iter_first(model, &iter);

  if(mode)
  {
    num = th->get_torrent_info().num_pieces();
    for(int i = 0; i < num; ++i)
    th->piece_priority(i, 1);

    while(valid)
    {
      gtk_list_store_set(GTK_LIST_STORE(model), &iter,
        COL_F_PRIORITY, 1,
        COL_F_NEW_PRI, 1,
      -1);
      valid = gtk_tree_model_iter_next(model, &iter);
    }
  }
  else
  {
    std::vector<int> * pri = new std::vector<int>(num);
    std::vector<int>::iterator pri_iter = pri->begin();
    gint value;

    valid = gtk_tree_model_get_iter_first(model, &iter);
    while(valid)
    {
      gtk_tree_model_get(model, &iter,
          COL_F_NEW_PRI, &value,
        -1
      );
      *pri_iter = value;
      valid = gtk_tree_model_iter_next(model, &iter);
      ++pri_iter;
    }
    th->prioritize_files(*pri);

    pri_iter = pri->begin();
    valid = gtk_tree_model_get_iter_first(model, &iter);
    while(valid)
    {
      gtk_list_store_set(GTK_LIST_STORE(model), &iter,
        COL_F_PRIORITY, *pri_iter,
        COL_F_NEW_PRI, 1,
      -1);
      valid = gtk_tree_model_iter_next(model, &iter);
      ++pri_iter;
    }
  }
}
